/* ******************************************************************
 * name LCD_driver.c
 *
 * author: Duy Vinh
 *
 * description: provide function to work with LCD16x2
 * ******************************************************************/

#include <CoOS.h>
#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <Config.h>
#include <LCD_driver.h>

void ConfigPinDB7Input(void);
void ConfigPinDB7Output(void);
void LCD_BFwait(void);
void LCD_WriteCmd(uint8_t Cmd);
void LCD_SetDDRamAddress(uint8_t DDrAddr);

/*
 * *******************************************
 * config GPIO pins for LCD working
 * *******************************************
 */
void LCD_Pin_Configuration(void) {
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	//config LCD function pin controller: E, RW, RS
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_LCD_E | GPIO_Pin_LCD_RW
			| GPIO_Pin_LCD_RS;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	//config LCD DB pin controller
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_LCD_DAT0 | GPIO_Pin_LCD_DAT1
			| GPIO_Pin_LCD_DAT2 | GPIO_Pin_LCD_DAT3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
}

/*
 * *****************************************
 * config pin for DB7 as input
 * only for LCD_BFwait function
 * *****************************************
 */
void ConfigPinDB7Input(void) {
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_LCD_DAT3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
}

/*
 * *****************************************
 * config pin for DB7 as output
 * only for LCD_BFwait function
 * *****************************************
 */
void ConfigPinDB7Output(void) {
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_LCD_DAT3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
}

/*
 * *********************************************************
 * wait until LCD finish processing previous command.
 * *********************************************************
 */
void LCD_BFwait(void) {
	int loop = 0;
	uint8_t status = Bit_SET;
	//delayms(10);//10
	ConfigPinDB7Input();
	Reset_RS;
	Set_RW;
	while (1) {
		Set_E;
		status = GPIO_ReadInputDataBit(GPIO_LCD, GPIO_Pin_LCD_DAT3);
		Reset_E;
		if (status == Bit_RESET) {
			break;
		}
		if (loop >= 100) {
			break;
		}
		loop++;
		delayms(1);
	}

//	do {
//		Set_E;
//		status = GPIO_ReadInputDataBit(GPIO_LCD, GPIO_Pin_LCD_DAT3);
//		Reset_E;
//		delayms(1);
//		loop++;
//	} while ((status == Bit_SET) && (loop < 5));
	ConfigPinDB7Output();
}

/*
 * *************************************************
 * Clear all LCD
 * put cursor on first pos of first line
 * *************************************************
 */
void LCD_ClearDisplay(void) {
	uint8_t cmd = 0x1;
	LCD_WriteCmd(cmd);
}

/*
 * *************************************************
 * Clear one line of LCD
 * put cursor on first pos of line
 *
 * EntryMode auto change to Emode1
 * user should set EntryMode again as you like
 * *************************************************
 */
void LCD_ClearLine(uint8_t Y) {
	uint8_t baseAddr;
	uint8_t pos = 0;
	if ((Y == 1) || (Y == 0)) {
		LCD_EntryModeSet(EMode1);
		switch (Y) {
		case 0:
			baseAddr = 0x0;
			break;
		case 1:
			baseAddr = 0x40;
			break;
		default:
			break;
		}
		LCD_SetDDRamAddress(baseAddr);
		for (pos = 0; pos <= 39; pos++) {
			LCD_WriteData(' ');
		}
		LCD_SetDDRamAddress(baseAddr);
	}
}

/*
 * *********************************************
 * move cursor to 1st pos of 1st line
 * *********************************************
 */
void LCD_CursorHome(void) {
	uint8_t cmd = 0x2;
	LCD_WriteCmd(cmd);
}

/*
 * *********************************************
 * write a commande to LCD
 * *********************************************
 */
void LCD_WriteCmd(uint8_t Cmd) {
	LCD_BFwait();
	Reset_RS;
	Reset_RW;
	Set_E;
	GPIO_Write(GPIO_LCD, (GPIO_ReadOutputData(GPIO_LCD) & 0xFFF0) | Cmd >> 4);
	Reset_E;
	Set_E;
	GPIO_Write(GPIO_LCD, (GPIO_ReadOutputData(GPIO_LCD) & 0xFFF0) | (Cmd
			& 0x000F));
	Reset_E;
	Set_RW;
}

/*
 * *********************************************
 * write a data (character) to LCD
 * *********************************************
 */
void LCD_WriteData(uint8_t Cmd) {
	LCD_BFwait();
	Set_RS;
	Reset_RW;
	Set_E;
	GPIO_Write(GPIO_LCD, (GPIO_ReadOutputData(GPIO_LCD) & 0xFFF0) | Cmd >> 4);
	Reset_E;
	Set_E;
	GPIO_Write(GPIO_LCD, (GPIO_ReadOutputData(GPIO_LCD) & 0xFFF0) | (Cmd
			& 0x000F));
	Reset_E;
	Set_RW;
	LCD_BFwait();
}

/*
 * *******************************************************
 * set data length for control (4bit or 8bit)
 * set font size 5x8 or 5x10
 * *******************************************************
 */
void LCD_FunctionSet(uint8_t DL, uint8_t Fontsize) {
	uint8_t cmd = 0x28;
	if (DL == Mode8bit) {
		cmd = cmd | 0x10;
	}
	if (Fontsize == Font5x10) {
		cmd = cmd | 0x4;
	}
	LCD_WriteCmd(cmd);
}

/*
 * *******************************************************
 * set display on/off control:
 * 		Dc: display screen
 * 		Cc: cursor
 * 		Bc: blinking cursor
 * *******************************************************
 */
void LCD_DisplayControl(uint8_t Dc, uint8_t Cc, uint8_t Bc) {
	uint8_t cmd = 0x8;
	if (Dc == 1) {
		cmd = cmd | 0x4;
	}
	if (Cc == 1) {
		cmd = cmd | 0x2;
	}
	if (Bc == 1) {
		cmd = cmd | 0x1;
	}
	LCD_WriteCmd(cmd);
}

/*
 * *******************************************************
 * set EntryMode:
 * 		Emode1:
 * 		Emode2:
 * 		Emode3:
 * 		Emode4:
 * *******************************************************
 */
void LCD_EntryModeSet(uint8_t EMode) {
	uint8_t cmd = 0x4;
	switch (EMode) {
	case EMode1:
		cmd = cmd | 0x2;
		break;
	case EMode2:
		break;
	case EMode3:
		cmd = cmd | 0x3;
		break;
	case EMode4:
		cmd = cmd | 0x1;
		break;
	default:
		cmd = cmd | 0x2;
		break;
	}
	LCD_WriteCmd(cmd);
}

/*
 * ***********************************************
 * set DDRamAddr - set position of cursor
 * ***********************************************
 */
void LCD_SetDDRamAddress(uint8_t DDrAddr) {
	LCD_WriteCmd(DDrAddr | 0x80);
}

/*
 * ***********************************************
 * set cursor position on screen
 *
 * 		X: pos in line
 * 		Y: line
 * ***********************************************
 */
void LCD_GotoXY(int X, int Y) {
	uint8_t baseAddr;
	if (((Y >= 0) || (Y <= 1)) && ((X >= 0) || (X <= 39))) {
		switch (Y) {
		case 0:
			baseAddr = 0x0;
			break;
		case 1:
			baseAddr = 0x40;
			break;
		default:
			break;
		}
		LCD_SetDDRamAddress(baseAddr + X);
	}
}

/*
 * *********************************************
 * initialize LCD
 *
 * 		data length 4bit
 * 		font 5x8
 * 		EMode1
 * 		display screen on, cursor on
 * *********************************************
 */
void LCD_Init(void) {
	LCD_Pin_Configuration();
	LCD_BFwait();
	LCD_WriteCmd(0x33);
	LCD_BFwait();
	LCD_WriteCmd(0x32);
	LCD_FunctionSet(Mode4bit, Font5x8);
	LCD_ClearDisplay();
	LCD_EntryModeSet(EMode1);
	LCD_DisplayControl(1, 1, 0);
	LCD_CursorHome();
}
